/*******************************************************************************
 * Copyright (c) 2005, 2013 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Markus Schorn (Wind River Systems)
 *******************************************************************************/
package org.eclipse.cdt.core.parser.tests.ast2;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.cdt.core.dom.ast.ASTVisitor;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.IProblemBinding;
import org.eclipse.cdt.core.dom.parser.ISourceCodeParser;
import org.eclipse.cdt.core.dom.parser.c.ANSICParserExtensionConfiguration;
import org.eclipse.cdt.core.dom.parser.c.GCCParserExtensionConfiguration;
import org.eclipse.cdt.core.dom.parser.c.ICParserExtensionConfiguration;
import org.eclipse.cdt.core.dom.parser.cpp.ANSICPPParserExtensionConfiguration;
import org.eclipse.cdt.core.dom.parser.cpp.GPPParserExtensionConfiguration;
import org.eclipse.cdt.core.dom.parser.cpp.ICPPParserExtensionConfiguration;
import org.eclipse.cdt.core.parser.FileContent;
import org.eclipse.cdt.core.parser.IScanner;
import org.eclipse.cdt.core.parser.ParserLanguage;
import org.eclipse.cdt.core.parser.ParserMode;
import org.eclipse.cdt.core.parser.ScannerInfo;
import org.eclipse.cdt.internal.core.dom.parser.c.CVisitor;
import org.eclipse.cdt.internal.core.dom.parser.c.GNUCSourceParser;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTNameBase;
import org.eclipse.cdt.internal.core.dom.parser.cpp.GNUCPPSourceParser;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPVisitor;
import org.eclipse.cdt.internal.core.parser.ParserException;

/**
 * @author dsteffle
 */
public class AST2SpecTestBase extends AST2TestBase {
	public AST2SpecTestBase() {
		super();
	}

	public AST2SpecTestBase(String name) {
		super(name);
	}

	/**
	 * checkSemantics is used to specify whether the example should have semantics checked
	 * since several spec examples have syntactically correct code ONLY this flag was added
	 * so that future tests can ensure that examples are checked against syntax/semantics where necessary
	 * @param code
	 * @param expectedProblemBindings the number of problem bindings you expect to encounter
	 * @throws ParserException
	 */
	protected void parseCandCPP(String code, boolean checkBindings, int expectedProblemBindings) throws ParserException {
		parse(code, ParserLanguage.C, false, true, checkBindings, expectedProblemBindings, null);
		parse(code, ParserLanguage.CPP, false, true, checkBindings, expectedProblemBindings, null);
	}

	protected IASTTranslationUnit parseWithErrors(String code, ParserLanguage lang) throws ParserException {
    	return parse(code, lang, false, false, false, 0, null);
    }

	protected IASTTranslationUnit parse(String code, ParserLanguage lang, boolean checkBindings, int expectedProblemBindings) throws ParserException {
    	return parse(code, lang, false, true, checkBindings, expectedProblemBindings, null);
    }
	
	protected IASTTranslationUnit parse(String code, ParserLanguage lang, String[] problems) throws ParserException {
    	return parse(code, lang, false, true, true, problems.length, problems);
	}
    
    private IASTTranslationUnit parse(String code, ParserLanguage lang, boolean useGNUExtensions, boolean expectNoProblems, 
    		boolean checkBindings, int expectedProblemBindings, String[] problems) throws ParserException {
		// TODO beef this up with tests... i.e. run once with \n, and then run again with \r\n replacing \n ... etc
		// TODO another example might be to replace all characters with corresponding trigraph/digraph tests...
		
        FileContent codeReader = FileContent.create("<test-code>", code.toCharArray());
		return parse(codeReader, lang, useGNUExtensions, expectNoProblems, checkBindings, expectedProblemBindings, problems);
    }
	
//	private IASTTranslationUnit parse(IFile filename, ParserLanguage lang, boolean useGNUExtensions, boolean expectNoProblems) throws ParserException {
//		CodeReader codeReader=null;
//		try {
//			codeReader = new CodeReader(filename.getName(), filename.getContents());
//		} catch (IOException e) {
//			e.printStackTrace();
//		} catch (CoreException e) {
//			e.printStackTrace();
//		}
//		
//		return parse(codeReader, lang, useGNUExtensions, expectNoProblems);
//    }
	
	private IASTTranslationUnit parse(FileContent codeReader, ParserLanguage lang,
			boolean useGNUExtensions, boolean expectNoProblems, boolean checkBindings,
			int expectedProblemBindings, String[] problems) throws ParserException {
        ScannerInfo scannerInfo = new ScannerInfo();
        IScanner scanner= AST2TestBase.createScanner(codeReader, lang, ParserMode.COMPLETE_PARSE, scannerInfo);
        
        ISourceCodeParser parser2 = null;
        if (lang == ParserLanguage.CPP) {
            ICPPParserExtensionConfiguration config = null;
            if (useGNUExtensions)
            	config = new GPPParserExtensionConfiguration();
            else
            	config = new ANSICPPParserExtensionConfiguration();
            parser2 = new GNUCPPSourceParser(scanner, ParserMode.COMPLETE_PARSE, NULL_LOG, config);
        } else {
            ICParserExtensionConfiguration config = null;

            if (useGNUExtensions)
            	config = new GCCParserExtensionConfiguration();
            else
            	config = new ANSICParserExtensionConfiguration();
            
            parser2 = new GNUCSourceParser(scanner, ParserMode.COMPLETE_PARSE, 
            		NULL_LOG, config);
        }
        
        if (expectedProblemBindings > 0)
    		CPPASTNameBase.sAllowNameComputation= true;

        IASTTranslationUnit tu = parser2.parse();
		
		// resolve all bindings
		if (checkBindings) {
			NameResolver res = new NameResolver();
	        tu.accept(res);
			if (res.problemBindings.size() != expectedProblemBindings)
				throw new ParserException("Expected " + expectedProblemBindings +
						" problems, encountered " + res.problemBindings.size()); 
			if (problems != null) {
				for (int i = 0; i < problems.length; i++) {
					assertEquals(problems[i], res.problemBindings.get(i));
				}
			}
		}

        if (parser2.encounteredError() && expectNoProblems)
            throw new ParserException("FAILURE"); 
         
        if (lang == ParserLanguage.C && expectNoProblems) {
			if (CVisitor.getProblems(tu).length != 0) {
				throw new ParserException("CVisitor has AST Problems"); 
			}
			if (tu.getPreprocessorProblems().length != 0) {
				throw new ParserException("C TranslationUnit has Preprocessor Problems"); 
			}
        } else if (lang == ParserLanguage.CPP && expectNoProblems) {
			if (CPPVisitor.getProblems(tu).length != 0) {
				throw new ParserException("CPPVisitor has AST Problems"); 
			}
			if (tu.getPreprocessorProblems().length != 0) {
				throw new ParserException("CPP TranslationUnit has Preprocessor Problems"); 
			}
        }
        
        return tu;
	}
	
	static protected class NameResolver extends ASTVisitor {
		{
			shouldVisitNames = true;
		}
		
		public List<IASTName> nameList = new ArrayList<IASTName>();
		public List<String> problemBindings = new ArrayList<String>();
		public int numNullBindings = 0;
		
		@Override
		public int visit(IASTName name) {
			nameList.add(name);
			IBinding binding = name.resolveBinding();
			if (binding instanceof IProblemBinding)
				problemBindings.add(name.toString());
			if (binding == null)
				numNullBindings++;
			return PROCESS_CONTINUE;
		}
		
		public IASTName getName(int idx) {
			if (idx < 0 || idx >= nameList.size())
				return null;
			return nameList.get(idx);
		}
		
		public int size() { 
			return nameList.size(); 
		}
	}
}
